fix(dashboard): aggregate paired-device usage in the desktop Dashboard and menubar badge - #866
Conversation
… badge Fixes getagentseal#795: the desktop Dashboard showed only local-device cost while the menubar and web GUI aggregated across paired devices. Desktop app: - Add a Local/Combined Scope setting (Settings > General), mirroring the macOS menubar. Combined passes `--scope combined` to the CLI, forces the provider filter to all, and persists to localStorage. - Overview hero shows the paired-device aggregate (cost/calls/sessions) with a "Combined · N devices" caption and per-device breakdown when Combined is selected; detailed panels remain local (the combined payload carries totals only). Menubar: - The badge figure now reflects Combined scope instead of always showing the local total: refreshMenubarBadge fetches the combined payload for the badge period and the badge renders the cross-device aggregate, falling back to local when no combined payload is available.
iamtoruk
left a comment
There was a problem hiding this comment.
Fixes #795. Verified end to end. Root cause was exactly as described: the desktop spawned status --format menubar-json without --scope combined, so the CLI never ran paired-device aggregation. The fix wires a Local/Combined scope control through buildOverviewArgs (combined passes --scope combined and drops the provider filter, which the CLI rejects alongside it; the renderer forces provider='all' in that mode so nothing is lost), and makes the menubar badge fetch the combined payload best-effort — success is gated only on the local fetch, so a slow or unreachable peer degrades gracefully to the local figure rather than failing the badge.
Local scope (the default) is untouched. On the merged tree (main was 29 ahead, clean merge): tsc clean, desktop npm test 462/462, swift test 128 tests + all XCTest suites at 0 failures, including the new scope-persistence, combined-total, and local-fallback cases. Merging as the base for #867.
…he all-provider view buildDurablePeriod derived the today slice of the multi-day, all-provider headline from the unsliced whole-range parse, so a turn spanning local midnight kept its category and turn count anchored on its yesterday start. The per-call cost and calls bucketed onto today correctly, but By Activity and the JSON daily turn count lost the post-midnight half — categories summed to only the pre-midnight cost while the headline, By Model and By Project were right. Slice the today parse with filterProjectsByDays first, which re-anchors the straddling turn to its surviving today calls, so today's category cost lands on today. Category cost is the sum of the slice's own calls, so day-N + day-N+1 still equals the whole-range total (no over-count); the per-day turn-count split matches the cache side and the documented per-day semantics. Adds a regression test in the straddling-turn conservation suite (mutation-checked: fails on the pre-fix code). Also fills in the CHANGELOG Unreleased entries for the batch (#853, #856, #872, #846/#859, #866/#867, #833).
Summary
Fixes #795. The desktop app's Dashboard showed only the local device's
cost, while the menu bar statistics and the web GUI reported usage
aggregated across paired devices. This makes the desktop Dashboard consistent
with those surfaces by adding a Local / Combined scope control, mirroring
the macOS menu bar's existing Scope setting.
Root cause: the desktop spawned
codeburn status --format menubar-jsonwithout
--scope combined, so the CLI never ran its paired-deviceaggregation and the renderer only ever had local data.
What changed
Desktop app
localStorage, defaultLocal), mirroring the menu bar.--scope combinedto the CLI, forces the provider filter toall, and clears the Claude-config scope (the CLI rejects--scope combinedalongside a provider/project filter).
sessions) with a "Combined · N devices" caption and a per-device
breakdown when Combined is selected.
Menu bar
local total: it fetches the combined payload for the badge's period and
renders the cross-device aggregate, falling back to local when no combined
payload is available (cold cache, or an unreachable peer).
Scope / limitations
etc.) remain local, because the combined payload carries totals only — this
matches the menu bar's behavior.
while it is reachable. An unreachable peer degrades gracefully to the
reachable subset.
Testing
npm run typecheckandnpm run build:electronclean;npm test→ 445 passing (+9 new covering the scope argv, the Settings control,and the combined hero).
swift buildclean; newAppStorescope tests added (rununder the Xcode toolchain:
swift test).Combined reported the aggregate ($163.54 month) vs local-only ($95.94),
matching the menu bar and web GUI.